**直方圖 (Histogram Chart)和長條圖 (Line Chart)**有些微的不同,在「Day09 練習題準備 - 圖表類型的理解」中曾經討論過圖表的分類。長條圖的資料點是離散的,而直方圖是連續的資料形式,每一個點代表的是一個區間的資料,可以用長條、或者線(較多資料點時適用)的方式來呈現。
首先準備一組直方圖的資料,用d3.random.normal()
產生隨機的常態分佈資料。
var rand = d3.random.normal(170, 10)
var dataSet = []
for(var i=0; i<100; i++){
dataSet.push(rand())
}
加入svg
var width = 500
var height = 380
var padding = {top:30, right:30, bottom:30, left:30}
var svg = d3.select("body").append("svg")
.attr("width",width)
.attr("height",height)
D3也提供了直方圖的layout,語法為d3.layout.histogram()
。
histogram.range([min, max])
設定資料分佈的範圍histogram.bins(count)
設定長條圖個數histogram.frequency(bool)
true為數量、false為機率
var binNum = 10, rangeMin = 130, rangeMax = 210
var histogram = d3.layout.histogram()
.range([rangeMin, rangeMax])
.bins(binNum)
.frequency(true)
var hisData = histogram(dataSet)
把hisData印出來後可看到資料結構如下圖。
其中x
為資料區間的下限值、dx
為區間的長度、y
為落到此區間的數量。
接著要建立比例尺,由於x軸的資料為離散的,所以要使用的是序數比例尺(Ordinal Scale),而y軸要使用的是線性比例尺(Linear Scale)。
在前面的文章已多次練習過線性比例尺(可參考Day13、Day14、Day16文章)。
序數比例尺使用方式有些許不同,序數比例尺的定義域以及值域都是離散的,在定義域放入剛才以直方圖layout轉換出來的x;而值域放入座標值。
其中值域這邊使用的方法是ordinal.ordinalRoundBands([min, max])
。rangeRoundBands
和rangeBands
類似,都是可以放入資料區間(如使用range則需放入所有的各別資料),而rangeRoundBands
的差別是會將結果取整數。
var xAxisWidth = width - padding.left - padding.right
var yAxisWidth = 450
var xTicks = hisData.map(function(d){ return d.x }) // 直方圖layout轉換出來的資料中的x
var xScale = d3.scale.ordinal() // 序數比例尺
.domain(xTicks) // 定義域
.rangeRoundBands([0, xAxisWidth]) // 值域
var yScale = d3.scale.linear() // 線性比例尺
.domain([d3.min(hisData, function(d){ return d.y }), d3.max(hisData, function(d){ return d.y })]) // 定義域
.range([5, yAxisWidth]) // 值域
接著開始繪製x軸刻度
var xAxis = d3.svg.axis()
.scale(xScale)
.orient("bottom")
svg.append("g")
.attr("class", "xAxis")
.attr("transform", "translate(" + padding.left + "," + (height - padding.bottom) + ")")
.call(xAxis)
以svg元素<rect>
繪製直方圖。
其中data()放入的就是layout所轉換出來的資料,設定屬性x
、y
、height
由layout資料中的x、y資料使用比例尺函式做計算。
而width屬性使用比例尺所提供的rangeBand()
函式取得每一段的寬度。
var gRect = svg.append("g")
.attr("transform", "translate(" + padding.left + "," + (-padding.bottom) + ")")
gRect.selectAll("rect")
.data(hisData)
.enter()
.append("rect")
.attr("class", "rect")
.attr("x", function(d,i){
return xScale(d.x)
})
.attr("y", function(d,i){
return height - yScale(d.y)
})
.attr("width", function(d,i){
return xScale.rangeBand()
})
.attr("height", function(d){
return yScale(d.y)
})
結果如下圖。
序數比例尺中的rangeRoundBands
可以選擇加入第2及第3個參數,第2個參數是所有區間之間的間距(padding)、第3個參數是單獨設定最左和最右兩邊的間距。
間距需輸入0~1之間的數值,單位為區間寬度的倍數。比如說,間距設為0.1時間距寬度即為區間寬度的1/10,如設為1、則間距和區間寬度會相等(區間會不見)。
以下修改原程式碼,將間距設為0.1、左右兩邊的間距設為0.3。
var xScale = d3.scale.ordinal()
.domain(xTicks)
.rangeRoundBands([0, xAxisWidth],0.1,0.3)
結果如下圖。
線上檢視連結可參考:http://jsfiddle.net/upstairs0102/dnfye7mv/